#include "src/scene/cuboidPrimitive.h"

#include <GL/gl.h>
#include <vector>
#include <iostream>
#include <algorithm>

#include "src/math/vector.h"

#include "src/defines/primitive.h"
#include "src/defines/axes.h"
#include "src/defines/rayState.h"

CuboidPrimitive::CuboidPrimitive(int _id) : Object(_id){
//    std::cout <<"1" << std::endl;
    min = Vector3D(-1, -1, -1);
    max = Vector3D(1, 1, 1);
    setBoundingBox();
}

CuboidPrimitive::CuboidPrimitive(const CuboidPrimitive &_c, int _id)
    : Object(_c.material, _c.center, _id, CUBOID, true, _c.transformation), min(_c.min), max(_c.max){
//    std::cout <<"2" << std::endl;
        setBoundingBox();
}

CuboidPrimitive::CuboidPrimitive(const Material &_m, const Vector3D &_min, const Vector3D &_max, int _id)
    : Object(_m, _id, CUBOID, true), min(_min), max(_max){
//    std::cout <<"3" << std::endl;
        center = (min + max) * (1.0/2.0);
        setBoundingBox();
//    min = min - center;
//    max = max - center;
//    transformation.addTranslation(center);
//    relativePosition = center = center - center;
}

CuboidPrimitive::CuboidPrimitive(const Vector3D &_min, const Vector3D &_max, const Vector3D &_center, const Material &_m, const Matrix &_matrix, const Matrix &_matrixI, int _id)
    : Object(_m, _center, _id, CUBOID, true, _matrix, _matrixI), min(_min), max(_max){
//    std::cout <<"4" << std::endl;
        setBoundingBox();
}

CuboidPrimitive::~CuboidPrimitive(){
}

void CuboidPrimitive::setMinMax(const Vector3D & _min, const Vector3D &_max){
    min = _min;
    max = _max;
    center = (min + max) * (1.0/2.0);
//    min = min - center;
//    max = max - center;
//    transformation.addTranslation(center);
//    relativePosition = center = center - center;
}

double CuboidPrimitive::volume() const{
    return (max.x - min.x)*(max.y - min.y)*(max.z - min.z);
}

double CuboidPrimitive::area() const{
    double inX = max.x - min.x;
    double inY = max.y - min.y;
    double inZ = max.z - min.z;
    return 2*((inX * inY) + (inX * inZ) + (inY * inZ));
}

void CuboidPrimitive::draw(bool _wireframe, bool _useParentColor, const Material &_parentColor) const{

    double minTexU, maxTexU, minTexV, maxTexV;
    bool useTexture = drawBegin(_wireframe, _useParentColor, _parentColor, &minTexU, &maxTexU, &minTexV, &maxTexV);

    drawBox(min, max, useTexture, minTexU, maxTexU, minTexV, maxTexV);

    drawEnd(_wireframe, useTexture);
}

std::vector<Trio> CuboidPrimitive::intersection(const Ray &_ray) const{
    std::vector<Trio> result;

    Vector3D origin = _ray.getOrigin();
    //inserindo o primeiro ponto que TODOS terão
    result.push_back(Trio(origin, Vector3D(1,0,0), OUT));

    //colocnado o raio para coordenadas da primitiva
    Matrix toLocalVector = transformation.getWorldToLocal();
    Matrix toLocalDirection = transformation.getWorldToLocalDirection();

    origin = (toLocalVector * origin.toHomogeneousCoordinates()).fromHomogeneousCoordinates();
    Vector3D direction = Vector3D(toLocalDirection * Vector(_ray.getDirection()));



    std::vector<Trio> possibleCases;

    if(fabs(direction.z) >= ERROR){//olhando os planos em XY
        double tZmax = (max.z - origin.z) / direction.z;

        if(tZmax > -ERROR ){//é maior ou igual a zero
            Vector3D localP = origin + (direction * tZmax);
            Vector3D n(0,0,1);

            //testando se tah entre os Xmin e Xmax e Ymax e Ymin
            if( ( (localP.x + ERROR > min.x) && (localP.x - ERROR < max.x) ) &&
                ( (localP.y + ERROR > min.y) && (localP.y - ERROR < max.y) ) ){
                    possibleCases.push_back(Trio(tZmax, localP, n));
            }

        }

        double tZmin = (min.z - origin.z) / direction.z;

        if(tZmin > -ERROR ){//é maior ou igual a zero
            Vector3D localP = origin + (direction * tZmin);
            Vector3D n(0,0,-1);

            //testando se tah entre os Xmin e Xmin e Ymin e Ymin
            if( ( (localP.x + ERROR > min.x) && (localP.x - ERROR < max.x) ) &&
                ( (localP.y + ERROR > min.y) && (localP.y - ERROR < max.y) ) ){
                    bool inserted = false;
                    for(unsigned int i = 0; i < possibleCases.size(); i++){
                        if(tZmin < possibleCases.at(i).t){
                            possibleCases.insert(possibleCases.begin() + i, Trio(tZmin, localP, n));

                            inserted = true;
                            break;
                        }
                    }
                    if(!inserted){
                        possibleCases.push_back(Trio(tZmin, localP, n));
                    }
            }

        }
    }

    if(fabs(direction.y) >= ERROR){//olhando os planos em XZ
        double tYmax = (max.y - origin.y) / direction.y;

        if(tYmax > -ERROR ){//é maior ou igual a zero
            Vector3D localP = origin + (direction * tYmax);
            Vector3D n(0,1,0);

            //testando se tah entre os Xmin e Xmax e Zmax e Zmin
            if( ( (localP.x + ERROR > min.x) && (localP.x - ERROR < max.x) ) &&
                ( (localP.z + ERROR > min.z) && (localP.z - ERROR < max.z) ) ){
                    bool inserted = false;
                    for(unsigned int i = 0; i < possibleCases.size(); i++){
                        if(tYmax < possibleCases.at(i).t){
                            possibleCases.insert(possibleCases.begin() + i, Trio(tYmax, localP, n));

                            inserted = true;
                            break;
                        }
                    }
                    if(!inserted){
                        possibleCases.push_back(Trio(tYmax, localP, n));
                    }
            }

        }

        double tYmin = (min.y - origin.y) / direction.y;

        if(tYmin > -ERROR ){//é maior ou igual a zero
            Vector3D localP = origin + (direction * tYmin);
            Vector3D n(0,-1,0);

            //testando se tah entre os Xmin e Xmin e Zmin e Zmin
            if( ( (localP.x + ERROR > min.x) && (localP.x - ERROR < max.x) ) &&
                ( (localP.z + ERROR > min.z) && (localP.z - ERROR < max.z) ) ){
                    bool inserted = false;
                    for(unsigned int i = 0; i < possibleCases.size(); i++){
                        if(tYmin < possibleCases.at(i).t){
                            possibleCases.insert(possibleCases.begin() + i, Trio(tYmin, localP, n));

                            inserted = true;
                            break;
                        }
                    }
                    if(!inserted){
                        possibleCases.push_back(Trio(tYmin, localP, n));
                    }
            }

        }
    }

    if(fabs(direction.x) >= ERROR){//olhando os planos em YZ
        double tXmax = (max.x - origin.x) / direction.x;

        if(tXmax > -ERROR ){//é maior ou igual a zero
            Vector3D localP = origin + (direction * tXmax);
            Vector3D n(1,0,0);

            //testando se tah entre os Ymin e Ymax e Zmax e Zmin
            if( ( (localP.y + ERROR > min.y) && (localP.y - ERROR < max.y) ) &&
                ( (localP.z + ERROR > min.z) && (localP.z - ERROR < max.z) ) ){
                    bool inserted = false;
                    for(unsigned int i = 0; i < possibleCases.size(); i++){
                        if(tXmax < possibleCases.at(i).t){
                            possibleCases.insert(possibleCases.begin() + i, Trio(tXmax, localP, n));

                            inserted = true;
                            break;
                        }
                    }
                    if(!inserted){
                        possibleCases.push_back(Trio(tXmax, localP, n));
                    }
            }

        }

        double tXmin = (min.x - origin.x) / direction.x;

        if(tXmin > -ERROR ){//é maior ou igual a zero
            Vector3D localP = origin + (direction * tXmin);
            Vector3D n(-1,0,0);

            //testando se tah entre os Ymin e Ymin e Zmin e Zmin
            if( ( (localP.y + ERROR > min.y) && (localP.y - ERROR < max.y) ) &&
                ( (localP.z + ERROR > min.z) && (localP.z - ERROR < max.z) ) ){
                    bool inserted = false;
                    for(unsigned int i = 0; i < possibleCases.size(); i++){
                        if(tXmin < possibleCases.at(i).t){
                            possibleCases.insert(possibleCases.begin() + i, Trio(tXmin, localP, n));

                            inserted = true;
                            break;
                        }
                    }
                    if(!inserted){
                        possibleCases.push_back(Trio(tXmin, localP, n));
                    }
            }

        }
    }


//    std::cout <<"possibleCases.size() = " << possibleCases.size() <<std::endl;

    Matrix toWorld = transformation.getLocalToWorld();
    Matrix toLocalTranpost = transformation.getWorldToLocalDirection().transpose();

    switch(possibleCases.size()){
        case 0:{
            break;
        }
        case 1:{//casos: saindo de dentro e cortanod um unico plano
            result.back().type = IN;

            if( fabs(possibleCases.front().t) < ERROR ){
                result.back().type = ON;
                result.back().normal = Vector3D(toLocalTranpost * Vector(possibleCases.at(0).normal)).normalize();
            }

            result.push_back(Trio((toWorld * (possibleCases.front().point).toHomogeneousCoordinates()).fromHomogeneousCoordinates(),
                                  Vector3D(toLocalTranpost * Vector(possibleCases.at(0).normal)).normalize(), OUT));
            break;
        }
        case 2:{
            //casos: se o centro tiver no meio , sai por uma aresta, se não é atravessando duas faces
            if( ( (origin.x + ERROR > min.x) && (origin.x - ERROR < max.x) ) &&
                ( (origin.y + ERROR > min.y) && (origin.y - ERROR < max.y) ) &&
                ( (origin.z + ERROR > min.z) && (origin.z - ERROR < max.z) ) ){
                    result.back().type = IN;
            }

            if( fabs(possibleCases.at(0).t) < ERROR ){
                result.back().type = ON;
                result.back().normal = Vector3D(toLocalTranpost * Vector(possibleCases.at(0).normal)).normalize();
                if( fabs(possibleCases.at(1).t) < ERROR ){
                    result.push_back(Trio((toWorld * (possibleCases.at(1).point).toHomogeneousCoordinates()).fromHomogeneousCoordinates(),
                                          Vector3D(toLocalTranpost * Vector(possibleCases.at(1).normal)).normalize(), OUT));
                    break;
                }
            }
//            std::cout << "possibleCases.at(0).point.z = " << possibleCases.at(0).point.z << "\tmin.z = "<< min.z <<std::endl;
//            std::cout << "possibleCases.at(1).point.z = " << possibleCases.at(1).point.z << "\tmin.z = "<< min.z <<std::endl;
            if( ( (fabs(possibleCases.at(0).point.z - min.z) < ERROR) && (fabs(possibleCases.at(1).point.z - min.z) < ERROR) ) ||
                ( (fabs(possibleCases.at(0).point.z - max.z) < ERROR) && (fabs(possibleCases.at(1).point.z - max.z) < ERROR) ) ||
                ( (fabs(possibleCases.at(0).point.y - min.y) < ERROR) && (fabs(possibleCases.at(1).point.y - min.y) < ERROR) ) ||
                ( (fabs(possibleCases.at(0).point.y - max.y) < ERROR) && (fabs(possibleCases.at(1).point.y - max.y) < ERROR) ) ||
                ( (fabs(possibleCases.at(0).point.x - min.x) < ERROR) && (fabs(possibleCases.at(1).point.x - min.x) < ERROR) ) ||
                ( (fabs(possibleCases.at(0).point.x - max.x) < ERROR) && (fabs(possibleCases.at(1).point.x - max.x) < ERROR) ) ) {
                    result.push_back(Trio((toWorld * (possibleCases.at(0).point).toHomogeneousCoordinates()).fromHomogeneousCoordinates(),
                                          Vector3D(toLocalTranpost * Vector(possibleCases.at(0).normal)).normalize(), ON));
                    result.push_back(Trio((toWorld * (possibleCases.at(1).point).toHomogeneousCoordinates()).fromHomogeneousCoordinates(),
                                          Vector3D(toLocalTranpost * Vector(possibleCases.at(1).normal)).normalize(), OUT));
                    break;
            }
            result.push_back(Trio((toWorld * (possibleCases.at(0).point).toHomogeneousCoordinates()).fromHomogeneousCoordinates(),
                                  Vector3D(toLocalTranpost * Vector(possibleCases.at(0).normal)).normalize(), IN));
            result.push_back(Trio((toWorld * (possibleCases.at(1).point).toHomogeneousCoordinates()).fromHomogeneousCoordinates(),
                                  Vector3D(toLocalTranpost * Vector(possibleCases.at(1).normal)).normalize(), OUT));
            break;
        }
        case 3:{
            //casos: saindo de dentor e chegnaod num canto,
            //       passando por uma areta e saindo por uma face, ou o contrario
            if( ( (origin.x + ERROR > min.x) && (origin.x - ERROR < max.x) ) &&
                ( (origin.y + ERROR > min.y) && (origin.y - ERROR < max.y) ) &&
                ( (origin.z + ERROR > min.z) && (origin.z - ERROR < max.z) ) ){
                    result.back().type = IN;
            }

            if( fabs(possibleCases.at(0).t) < ERROR ){
                result.back().type = ON;
                result.back().normal = Vector3D(toLocalTranpost * Vector(possibleCases.at(0).normal)).normalize();
                if( fabs(possibleCases.at(2).t) < ERROR ){
                    result.push_back(Trio((toWorld * (possibleCases.at(2).point).toHomogeneousCoordinates()).fromHomogeneousCoordinates(),
                                          Vector3D(toLocalTranpost * Vector(possibleCases.at(2).normal)).normalize(), OUT));
                    break;
                }
            }
            if( ( (fabs(possibleCases.at(0).point.z - min.z) < ERROR) && (fabs(possibleCases.at(2).point.z - min.z) < ERROR) ) ||
                ( (fabs(possibleCases.at(0).point.z - max.z) < ERROR) && (fabs(possibleCases.at(2).point.z - max.z) < ERROR) ) ||
                ( (fabs(possibleCases.at(0).point.y - min.y) < ERROR) && (fabs(possibleCases.at(2).point.y - min.y) < ERROR) ) ||
                ( (fabs(possibleCases.at(0).point.y - max.y) < ERROR) && (fabs(possibleCases.at(2).point.y - max.y) < ERROR) ) ||
                ( (fabs(possibleCases.at(0).point.x - min.x) < ERROR) && (fabs(possibleCases.at(2).point.x - min.x) < ERROR) ) ||
                ( (fabs(possibleCases.at(0).point.x - max.x) < ERROR) && (fabs(possibleCases.at(2).point.x - max.x) < ERROR) ) ) {
                    result.push_back(Trio((toWorld * (possibleCases.at(0).point).toHomogeneousCoordinates()).fromHomogeneousCoordinates(),
                                          Vector3D(toLocalTranpost * Vector(possibleCases.at(0).normal)).normalize(), ON));
                    result.push_back(Trio((toWorld * (possibleCases.at(2).point).toHomogeneousCoordinates()).fromHomogeneousCoordinates(),
                                          Vector3D(toLocalTranpost * Vector(possibleCases.at(2).normal)).normalize(), OUT));
                    break;
            }
            result.push_back(Trio((toWorld * (possibleCases.at(0).point).toHomogeneousCoordinates()).fromHomogeneousCoordinates(),
                                  Vector3D(toLocalTranpost * Vector(possibleCases.at(0).normal)).normalize(), IN));
            result.push_back(Trio((toWorld * (possibleCases.at(2).point).toHomogeneousCoordinates()).fromHomogeneousCoordinates(),
                                  Vector3D(toLocalTranpost * Vector(possibleCases.at(2).normal)).normalize(), OUT));

            break;
        }
        case 4:{
            //casos: entrnado numa face e saindo num canto
            //       entrnado num canto e saindo por uma face
            //       entrnaodo e saindo por uma aresta

            if( fabs(possibleCases.at(0).t) < ERROR ){
                result.back().type = ON;
                result.back().normal = Vector3D(toLocalTranpost * Vector(possibleCases.at(0).normal)).normalize();
            }
            if( ( (fabs(possibleCases.at(0).point.z - min.z) < ERROR) && (fabs(possibleCases.at(3).point.z - min.z) < ERROR) ) ||
                ( (fabs(possibleCases.at(0).point.z - max.z) < ERROR) && (fabs(possibleCases.at(3).point.z - max.z) < ERROR) ) ||
                ( (fabs(possibleCases.at(0).point.y - min.y) < ERROR) && (fabs(possibleCases.at(3).point.y - min.y) < ERROR) ) ||
                ( (fabs(possibleCases.at(0).point.y - max.y) < ERROR) && (fabs(possibleCases.at(3).point.y - max.y) < ERROR) ) ||
                ( (fabs(possibleCases.at(0).point.x - min.x) < ERROR) && (fabs(possibleCases.at(3).point.x - min.x) < ERROR) ) ||
                ( (fabs(possibleCases.at(0).point.x - max.x) < ERROR) && (fabs(possibleCases.at(3).point.x - max.x) < ERROR) ) ) {
                    result.push_back(Trio((toWorld * (possibleCases.at(0).point).toHomogeneousCoordinates()).fromHomogeneousCoordinates(),
                                          Vector3D(toLocalTranpost * Vector(possibleCases.at(0).normal)).normalize(), ON));
                    result.push_back(Trio((toWorld * (possibleCases.at(3).point).toHomogeneousCoordinates()).fromHomogeneousCoordinates(),
                                          Vector3D(toLocalTranpost * Vector(possibleCases.at(3).normal)).normalize(), OUT));
                    break;
            }
            result.push_back(Trio((toWorld * (possibleCases.at(0).point).toHomogeneousCoordinates()).fromHomogeneousCoordinates(),
                                  Vector3D(toLocalTranpost * Vector(possibleCases.at(0).normal)).normalize(), IN));
            result.push_back(Trio((toWorld * (possibleCases.at(3).point).toHomogeneousCoordinates()).fromHomogeneousCoordinates(),
                                  Vector3D(toLocalTranpost * Vector(possibleCases.at(3).normal)).normalize(), OUT));

            break;
        }
        case 5:{
            //casos: entrnado numa aresta e saindo por um canto

            if( fabs(possibleCases.at(0).t) < ERROR ){
                result.back().type = ON;
                result.back().normal = Vector3D(toLocalTranpost * Vector(possibleCases.at(0).normal)).normalize();
            }
            result.push_back(Trio((toWorld * (possibleCases.at(0).point).toHomogeneousCoordinates()).fromHomogeneousCoordinates(),
                                  Vector3D(toLocalTranpost * Vector(possibleCases.at(0).normal)).normalize(), IN));
            result.push_back(Trio((toWorld * (possibleCases.at(4).point).toHomogeneousCoordinates()).fromHomogeneousCoordinates(),
                                  Vector3D(toLocalTranpost * Vector(possibleCases.at(4).normal)).normalize(), OUT));

            break;
        }
        case 6:{
            //casos: entrnado numa aresta e saindo por um canto

            if( fabs(possibleCases.at(0).t) < ERROR ){
                result.back().type = ON;
                result.back().normal = Vector3D(toLocalTranpost * Vector(possibleCases.at(0).normal)).normalize();
            }
            result.push_back(Trio((toWorld * (possibleCases.at(0).point).toHomogeneousCoordinates()).fromHomogeneousCoordinates(),
                                  Vector3D(toLocalTranpost * Vector(possibleCases.at(0).normal)).normalize(), IN));
            result.push_back(Trio((toWorld * (possibleCases.at(5).point).toHomogeneousCoordinates()).fromHomogeneousCoordinates(),
                                  Vector3D(toLocalTranpost * Vector(possibleCases.at(5).normal)).normalize(), OUT));

            break;
        }
    }
    return result;

}

void CuboidPrimitive::setBoundingBox() {
    boundingBoxWithTransformation(min, max);
}
/*
         p6------p7 max
         /|      /|
        p2------P3|
        |P4-----|-p5
        |/      |/
    min p0------p1
    z
    |  y
    |/_ _ x

*/


void CuboidPrimitive::getTextureUV(double *_u, double *_v, const Vector3D &_point, unsigned int _type) const
{
    Matrix toLocal = transformation.getWorldToLocal();
    Vector3D localPoint = (toLocal * (_point).toHomogeneousCoordinates()).fromHomogeneousCoordinates();

    if( fabs(localPoint.z - 1) < ERROR ) localPoint.z = 1;
    else if( fabs(localPoint.z) < ERROR ) localPoint.z = 0;
    if( fabs(localPoint.y - 1) < ERROR ) localPoint.y = 1;
    else if( fabs(localPoint.y) < ERROR ) localPoint.y = 0;
    if( fabs(localPoint.x - 1) < ERROR ) localPoint.x = 1;
    else if( fabs(localPoint.x) < ERROR ) localPoint.x = 0;
    switch(_type)
    {
        default:
        case ORIGINAL_TEXTURE:
        {
            if(localPoint.z == 1)//tampas
            {
                *_u = localPoint.x;
                *_v = 1 - localPoint.y;
            }
            else if(localPoint.z == 0)//tampas
            {
                *_u = localPoint.x;
                *_v = localPoint.y;
            }
            else if(localPoint.y == 1)//tampas
            {
                *_u = 1 - localPoint.x;
                *_v = 1 - localPoint.z;
            }
            else if(localPoint.y == 0)//tampas
            {
                *_u = localPoint.x;
                *_v = 1 - localPoint.z;
            }
            else if(localPoint.x == 1)//tampas
            {
                *_u = localPoint.y;
                *_v = 1 - localPoint.z;
            }
            else// if(localPoint.x == 0)//tampas
            {
                *_u = 1 - localPoint.y;
                *_v = 1 - localPoint.z;
            }
            break;
        }
        case PLANAR_TEXTURE:
        {
            *_u = localPoint.x;
            *_v = 1 - localPoint.y;
            break;
        }
        case SPHERICAL_TEXTURE:
        {
            *_u = 0.5 + (atan2((localPoint.y*2.0 - 1), (localPoint.x*2.0 - 1))/(2*M_PI));
            *_v = 0.5 - (asin((localPoint.z*2.0 - 1))/(M_PI));
            break;
        }
    }
}
